// Подключаем заголовочный файл gtest.h для использования фреймворка
#include <gtest/gtest.h>

// Определяем функцию, которая вычисляет факториал числа
int Factorial(int n) {
  // Если n меньше нуля, бросаем исключение
  if (n < 0) {
    throw std::invalid_argument("n must be non-negative");
  }
  // Если n равно нулю или единице, возвращаем единицу
  if (n == 0 || n == 1) {
    return 1;
  }
  // Иначе, возвращаем произведение n и факториала от n-1
  return n * Factorial(n - 1);
}

// Определяем класс, который реализует стек на основе массива
template <typename T>
class Stack {
 public:
  // Конструктор, который принимает размер стека
  Stack(int size) {
    // Если размер меньше или равен нулю, бросаем исключение
    if (size <= 0) {
      throw std::invalid_argument("size must be positive");
    }
    // Выделяем память под массив элементов
    array = new T[size];
    // Инициализируем индекс вершины стека
    top = -1;
    // Инициализируем максимальный размер стека
    max_size = size;
  }

  // Деструктор, который освобождает память от массива элементов
  ~Stack() {
    delete[] array;
  }

  // Метод, который добавляет элемент в стек
  void push(T value) {
    // Если стек полон, бросаем исключение
    if (isFull()) {
      throw std::overflow_error("stack is full");
    }
    // Увеличиваем индекс вершины стека
    top++;
    // Сохраняем элемент в массиве
    array[top] = value;
  }

  // Метод, который удаляет элемент из стека
  T pop() {
    // Если стек пуст, бросаем исключение
    if (isEmpty()) {
      throw std::underflow_error("stack is empty");
    }
    // Запоминаем элемент в вершине стека
    T value = array[top];
    // Уменьшаем индекс вершины стека
    top--;
    // Возвращаем элемент
    return value;
  }

  // Метод, который проверяет, пуст ли стек
  bool isEmpty() {
    // Стек пуст, если индекс вершины стека равен -1
    return top == -1;
  }

  // Метод, который проверяет, полон ли стек
  bool isFull() {
    // Стек полон, если индекс вершины стека равен максимальному размеру минус один
    return top == max_size - 1;
  }

 private:
  // Массив элементов стека
  T* array;
  // Индекс вершины стека
  int top;
  // Максимальный размер стека
  int max_size;
};

// Определяем тестовый случай FactorialTest
TEST(FactorialTest, HandlesZeroInput) {
  // Проверка, что факториал от нуля равен единице
  EXPECT_EQ(1, Factorial(0));
}

TEST(FactorialTest, HandlesPositiveInput) {
  // Проверка, что факториал от положительных чисел вычисляется правильно
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(3628800, Factorial(10));
}

TEST(FactorialTest, ThrowsExceptionOnNegativeInput) {
  // Проверка, что факториал от отрицательных чисел бросает исключение
  EXPECT_THROW(Factorial(-1), std::invalid_argument);
  EXPECT_THROW(Factorial(-5), std::invalid_argument);
}

// Определяем класс тестовой фикстуры StackTest
class StackTest : public ::testing::Test {
 protected:
  // Функция, которая вызывается перед каждым тестом
  void SetUp() override {
    // Создаем объект стека
    stack = new Stack<int>(10);
  }

  // Функция, которая вызывается после каждого теста
  void TearDown() override {
    // Удаляем объект стека
    delete stack;
  }

  // Указатель на объект стека
  Stack<int>* stack;
};

// Определяем тестовые функции с использованием тестовой фикстуры StackTest
TEST_F(StackTest, IsEmptyInitially) {
  // Проверка, что стек пуст изначально
  EXPECT_TRUE(stack->isEmpty());
}

TEST_F(StackTest, CanPushAndPop) {
  // Проверка, что стек может добавлять и удалять элементы
  stack->push(1);
  stack->push(2);
  stack->push(3);
  EXPECT_EQ(3, stack->pop());
  EXPECT_EQ(2, stack->pop());
  EXPECT_EQ(1, stack->pop());
  EXPECT_TRUE(stack->isEmpty());
}

TEST_F(StackTest, ThrowsExceptionOnOverflow) {
  // Проверка, что стек бросает исключение при переполнении
  for (int i = 0; i < 10; i++) {
    stack->push(i);
  }
  EXPECT_THROW(stack->push(10), std::overflow_error);
}

TEST_F(StackTest, ThrowsExceptionOnUnderflow) {
  // Проверка, что стек бросает исключение при опустошении
  EXPECT_THROW(stack->pop(), std::underflow_error);
}

// Запуск тестов
int main(int argc, char** argv) {
  // Инициализация GTest Framework
  ::testing::InitGoogleTest(&argc, argv);
  // Запуск всех тестов
  return RUN_ALL_TESTS();
}
